﻿using System;
using System.IO;
using System.Security.Permissions;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;

public class BufferedSender
{
    List<byte[]> buff = new List<byte[]>();

    public int getBufferItemsCount()
    {
        return buff.Count;
    }

    public int Send( byte data )
    {
        buff.Add(new byte[1] { data });
        return sizeof(byte);
    }

    public int Send(int data)
    {
        buff.Add( BitConverter.GetBytes(data) );
        return sizeof(int);
    }

    public int Send(byte[] data)
    {
        buff.Add(data);
        return data.Length;
    }

    public int Send(string data)
    {
        buff.Add(Encoding.ASCII.GetBytes(data));
        return data.Length;
    }

    public int sendBufferedData(Socket s)
    {
        int result = 0;
        foreach (byte[] d in buff)
        {
            s.Send(d);
            result += d.Length;
        }

        return result;
    }
    
}


public class Watcher
{

    static string watch_dir = "c:\\Users\\marcin\\workspace\\";
    static string host = "linux";
    static int port = 8022;
    static byte[] last_file = null;

    public static void Main()
    {
        connect("linux", 8022);
        Run();

    }

    private static Socket ConnectSocket(string server, int port)
    {
        Socket s = null;
        IPHostEntry hostEntry = null;

        // Get host related information.
        hostEntry = Dns.GetHostEntry(server);

        // Loop through the AddressList to obtain the supported AddressFamily. This is to avoid
        // an exception that occurs when the host IP Address is not compatible with the address family
        // (typical in the IPv6 case).
        foreach (IPAddress address in hostEntry.AddressList)
        {
            IPEndPoint ipe = new IPEndPoint(address, port);
            Socket tempSocket =
                new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            tempSocket.Connect(ipe);

            if (tempSocket.Connected)
            {
                s = tempSocket;
                break;
            }
            else
            {
                continue;
            }
        }
        return s;
    }

    // This method requests the home page content for the specified server.
    private static string SocketSendReceive(string server, int port)
    {
        string request = "GET / HTTP/1.1\r\nHost: " + server +
            "\r\nConnection: Close\r\n\r\n";
        Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
        Byte[] bytesReceived = new Byte[1024];

        // Create a socket connection with the specified server and port.
        Socket s = ConnectSocket(server, port);

        if (s == null)
            return ("Connection failed");

        // Send request to the server.
        s.Send(bytesSent, bytesSent.Length, 0);

        // Receive the server home page content.
        int bytes = 0;
        string page = "Default HTML page on " + server + ":\r\n";

        // The following will block until te page is transmitted.
        do
        {
            bytes = s.Receive(bytesReceived, bytesReceived.Length, 0);
            page = page + Encoding.ASCII.GetString(bytesReceived, 0, bytes);
        }
        while (bytes > 0);

        s.Close();

        return page;
    }

    private static void connect(string host, int port)
    {
        string result = "";// SocketSendReceive(host, port);
        Console.WriteLine(result);
    }

    private static void rCreateDir(BufferedSender s, string src)
    {
        byte [] cmd = {0};
        s.Send(cmd);
        s.Send(BitConverter.GetBytes(src.Length));
        s.Send(Encoding.ASCII.GetBytes(src));
    }

    private static void rCreateOrChangeFile(BufferedSender s, string src, string full_src)
    {
        byte [] cmd = {1};
        byte[] data = null;
        bool the_same_content = true;
        try
        {
            using (FileStream fsSource = new FileStream(full_src, FileMode.Open, FileAccess.Read))
            {
                data = new byte[fsSource.Length];
                int numBytesToRead = (int)fsSource.Length;
                int numBytesRead = 0;
                while (numBytesToRead > 0)
                {
                    int n = fsSource.Read(data, numBytesRead, numBytesToRead);
                    if (n == 0)
                        break;
                    numBytesRead += n;
                    numBytesToRead -= n;
                }
                fsSource.Close();
                numBytesToRead = data.Length;
            }
        }
        catch (FileNotFoundException ioEx)
        {
            data = null;
            Console.WriteLine(ioEx.Message);
            return;
        }
        catch (Exception e)
        {
            data = null;
            return;
        }
        if (data == null)
            return;
        the_same_content = (last_file != null && last_file.Length == data.Length);
        for (int x = 0; the_same_content && x < data.Length; x++)
            the_same_content = (data[x] == last_file[x]);
        last_file = data;
        if( the_same_content )
            return;        
        s.Send(cmd);
        s.Send(BitConverter.GetBytes(src.Length));
        s.Send(Encoding.ASCII.GetBytes(src));
        s.Send(BitConverter.GetBytes(data.Length));
        s.Send(data);
    }

    private static void rMvFileOrDir(BufferedSender s, string src, string dest)
    {
        byte[] cmd = {2};
        s.Send(cmd);
        s.Send(BitConverter.GetBytes(src.Length));
        s.Send(Encoding.ASCII.GetBytes(src));
        s.Send(BitConverter.GetBytes(dest.Length));
        s.Send(Encoding.ASCII.GetBytes(dest));
    }

    private static void rDeleteFileOrDir(BufferedSender s, string src)
    {
        byte[] cmd = {3};
        s.Send(cmd);
        s.Send(BitConverter.GetBytes(src.Length));
        s.Send(Encoding.ASCII.GetBytes(src));
    }

    private static void rCmd(string type, string p_src, string p_dest)
    {
        bool is_dir_exists = Directory.Exists(p_src) || Directory.Exists(p_dest);
        bool is_file_exists = File.Exists(p_src) || File.Exists(p_dest);
        if( !is_dir_exists )
            Console.WriteLine("dir not exists");
        if (!is_file_exists)
            Console.WriteLine("file not exists");
        if (!is_dir_exists && !is_file_exists && type != "op-Deleted")
            return;
        if (type == "op-Changed" && is_dir_exists)
            return;
        if( p_src.IndexOf("\\.") >= 0 )
            return;
        string src = p_src.Replace(watch_dir, "").Replace('\\', '/');
        string dest = (p_dest != null) ? p_dest.Replace(watch_dir, "").Replace('\\', '/') : null;

        BufferedSender bs = new BufferedSender();
        switch (type)
        {
            case "op-Created":
                if( is_file_exists )
                    rCreateOrChangeFile(bs, src, p_src);
                else if (is_dir_exists)
                    rCreateDir(bs, src);
                break;

            case "op-Changed":
                rCreateOrChangeFile(bs, src, p_src);
                break;

            case "op-rename":
                rMvFileOrDir(bs, src, dest);
                break;

            case "op-Deleted":
                rDeleteFileOrDir(bs, src);
                break;

        }

        if (bs.getBufferItemsCount() > 0)
        {
            Socket s = null;
            try
            {
                s = ConnectSocket(host, port);
                if (s == null)
                {
                    Console.WriteLine("Error while connect");
                    return;
                }
                bs.sendBufferedData(s);
            }
            catch (SocketException e)
            {
                Console.WriteLine("Connection error, {0} Error code: {1}.", e.Message, e.ErrorCode);
                return;
            }
            s.Close();
        }

    }

    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    public static void Run()
    {
        string[] args = System.Environment.GetCommandLineArgs();
        if(args.Length >= 2)
            watch_dir = args[1];
        if (args.Length >= 3)
            host = args[2];
        if (args.Length >= 4)
            port = Convert.ToInt32(args[3]);
        FileSystemWatcher watcher = new FileSystemWatcher();
        watcher.Path = watch_dir;
        watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        //NotifyFilters.LastAccess | 
        watcher.IncludeSubdirectories = true;
        watcher.Filter = "*.*";

        watcher.Created += new FileSystemEventHandler(OnChanged);
        watcher.Changed += new FileSystemEventHandler(OnChanged);
        watcher.Renamed += new RenamedEventHandler(OnRenamed);
        watcher.Deleted += new FileSystemEventHandler(OnChanged);
        watcher.EnableRaisingEvents = true;

        Console.WriteLine("Press \'q\' to quit.");
        while (Console.Read() != 'q') ;
    }

    // Define the event handlers.
    private static void OnChanged(object source, FileSystemEventArgs e)
    {
        // Specify what is done when a file is changed, created, or deleted.
        if( e.FullPath.IndexOf("\\.") >= 0 )
            return;
        Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
        rCmd( "op-" + e.ChangeType, e.FullPath, "");
    }

    private static void OnRenamed(object source, RenamedEventArgs e)
    {
        if (e.FullPath.IndexOf("\\.") >= 0)
            return;
        // Specify what is done when a file is renamed.
        Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
        rCmd("op-rename", e.OldFullPath, e.FullPath);
    }

    private static void ExecuteCommandSync(object command)
    {
        try
        {
            // create the ProcessStartInfo using "cmd" as the program to be run,
            // and "/c " as the parameters.
            // Incidentally, /c tells cmd that we want it to execute the command that follows,
            // and then exit.
            System.Diagnostics.ProcessStartInfo procStartInfo =
                new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

            // The following commands are needed to redirect the standard output.
            // This means that it will be redirected to the Process.StandardOutput StreamReader.
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.UseShellExecute = false;
            // Do not create the black window.
            procStartInfo.CreateNoWindow = true;
            // Now we create a process, assign its ProcessStartInfo and start it
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo = procStartInfo;
            proc.Start();
            // Get the output into a string
            string result = proc.StandardOutput.ReadToEnd();
            // Display the command output.
            Console.WriteLine(result);
        }
        catch (Exception objException)
        {
            // Log the exception
        }
    }

}
